#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <openssl/hmac.h>
#include <openssl/ecdsa.h>
#include <openssl/ecdh.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/sm2.h>
#include <string.h>

#include "csvcert.h"

#define OCA_FILENAME "oca.cert"
#define SIGNED_PEK_CSR_FILENAME "pek_csr.signed.cert"

/**
 * @brief  Validates a Platform Endorsement Key (PEK) Certificate Signing Request (CSR).
 *
 * @param  [in] pek_csr Pointer to the PEK CSR certificate to be validated.
 *
 * @returns 0 if the CSR is valid, -1 otherwise.
 *
 * @note This function checks whether the PEK CSR has the expected structure:
 *       - Version must be 1.
 *       - Public key usage must indicate a PEK.
 *       - Both signature fields (sig_1 and sig_2) must be invalid.
 */
int validate_pek_csr(csv_cert* pek_csr)
{
    if (pek_csr && pek_csr->version == 1 &&
        pek_csr->pub_key_usage == CSV_USAGE_PEK &&
        pek_csr->sig_1_usage == CSV_USAGE_INVALID &&
        pek_csr->sig_1_algo == CSV_SIG_ALGO_INVALID &&
        pek_csr->sig_2_usage == CSV_USAGE_INVALID &&
        pek_csr->sig_2_algo == CSV_SIG_ALGO_INVALID) {
        return 0;
    }
    return -1;
}

/**
 * @brief Writes data to a file.
 *
 * @param  [in] filename   The name of the file to write.
 * @param  [in] data       Pointer to the data buffer to write.
 * @param  [in] data_size  The size of the data buffer in bytes.
 *
 * @returns 0 on success, -1 if file opening fails, -2 if writing fails.
 */
int write_data_to_file(const char *filename, void *data, size_t data_size) {
    // Open the file for writing (binary mode)
    FILE *file = fopen(filename, "wb");
    if (file == NULL) {
        perror("Failed to open file");
        return -1;  // File open failed
    }

    // Write data to the file
    size_t written_size = fwrite(data, 1, data_size, file);
    if (written_size != data_size) {
        perror("Failed to write data to file");
        fclose(file);
        return -2;  // Write operation failed
    }

    // Close the file
    fclose(file);
    return 0;  // Success
}

/**
 * @brief Reads data from a file into a specified memory buffer.
 *
 * @param  [in]  filename     The name of the file to read.
 * @param  [out] buffer       Pointer to the buffer where the file data will be stored.
 * @param  [in]  buffer_size  The maximum size of the buffer in bytes.
 *
 * @returns 0 on success, -1 if file opening fails, -2 if reading fails.
 */
int read_data_from_file(const char *filename, void *buffer, size_t buffer_size) {
    // Open the file for reading in binary mode
    FILE *file = fopen(filename, "rb");
    if (file == NULL) {
        perror("Error opening file");
        return -1;  // Error opening file
    }

    // Read data from the file into the buffer
    size_t read_size = fread(buffer, 1, buffer_size, file);
    if (read_size != buffer_size) {
        if (read_size == 0) {
            perror("Error reading file: file might be empty or unreadable");
        } else {
            perror("Error reading data from file");
        }
        fclose(file);
        return -2;  // Error reading data
    }

    // Close the file
    fclose(file);
    return 0;  // Success
}

/**
 * @brief Handles the creation of an OCA certificate and signing of a PEK CSR.
 *
 * @param  [in] argc  The number of command-line arguments.
 * @param  [in] argv  The command-line arguments array.
 *
 * @returns EXIT_SUCCESS on success, EXIT_FAILURE on failure.
 *
 * @note This function performs the following tasks:
 *       - Parses input parameters.
 *       - Loads a PEK CSR certificate file.
 *       - Loads an OCA private key file.
 *       - Creates an OCA certificate.
 *       - Signs the PEK CSR with the OCA private key.
 *       - Saves the generated certificates to files.
 */
static int pek_oca_handler(int argc, char* argv[]) {
    int ret_val = EXIT_FAILURE;
    EVP_PKEY* oca_priv_key = NULL;
    csv_cert pek_csr;
    csv_cert* oca_cert = NULL;
    char* userid = NULL;

    /* Check parameters: need at least two arguments.
       If the first parameter is "--help", display help info. */
    if (argc < 3 || (argc > 1 && strcmp(argv[1], "--help") == 0)) {
        fprintf(stderr, "\nUsage:\n%s <pek_csr.cert> <oca_priv.key> [userid]\n\n", argv[0]);
        fprintf(stderr, "Description:\n");
        fprintf(stderr, "  Create an OCA certificate and sign a PEK CSR with an OCA private key.\n");
        fprintf(stderr, "  Outputs signed certificates: %s and %s.\n\n",
                OCA_FILENAME, SIGNED_PEK_CSR_FILENAME);
        fprintf(stderr, "Arguments:\n");
        fprintf(stderr, "  pek_csr.cert      The PEK CSR file, generated by the 'hag csv pek_csr' command.\n");
        fprintf(stderr, "  oca_priv.key      The private key file (generated with 'openssl ecparam -genkey -name SM2 -out oca_priv.key').\n");
        fprintf(stderr, "  userid            (Optional) User identifier; defaults to NULL if not provided.\n\n");
        return ret_val;
    }

    /* Handle the optional userid argument. Always allocate memory using strdup. */
    if (argc >= 4) {
        size_t userid_len = strlen(argv[3]);
        if (userid_len > SM2_UID_SIZE_U - sizeof(unsigned short)) {
            fprintf(stderr, "Error: User ID is too long. Maximum length is %ld characters.\n",
                    SM2_UID_SIZE_U - sizeof(unsigned short));
            goto cleanup;
        }
        userid = strdup(argv[3]);
        if (!userid) {
            fprintf(stderr, "Error: Memory allocation for User ID failed.\n");
            goto cleanup;
        }
    } else {
        userid = strdup("NULL");  /* Default value */
        if (!userid) {
            fprintf(stderr, "Error: Memory allocation for default User ID failed.\n");
            goto cleanup;
        }
    }

    /* Load the PEK CSR file */
    if (read_data_from_file(argv[1], &pek_csr, sizeof(csv_cert))) {
        fprintf(stderr, "Error: Failed to load PEK CSR file: %s\n", argv[1]);
        goto cleanup;
    }

    /* Load the OCA private key */
    if (read_priv_key_pem_into_evpkey(argv[2], &oca_priv_key)) {
        fprintf(stderr, "Error: Failed to load private key: %s\n", argv[2]);
        goto cleanup;
    }

    /* Create the OCA certificate */
    oca_cert = create_oca_cert(&oca_priv_key, pek_csr.api_major, pek_csr.api_minor, userid);
    if (!oca_cert) {
        fprintf(stderr, "Error: Failed to create OCA certificate.\n");
        goto cleanup;
    }

    /* Sign the PEK CSR */
    if (sign_with_key_in_sig2(&pek_csr, CSV_CERT_MAX_VERSION, CSV_USAGE_PEK,
                              CSV_SIG_ALGO_TYPE_SM2_SA, &oca_priv_key,
                              CSV_USAGE_OCA, CSV_SIG_ALGO_TYPE_SM2_SA, userid) == 0) {
        fprintf(stderr, "Error: PEK CSR signing failed.\n");
        goto cleanup;
    }

    /* Write the signed OCA certificate */
    if (write_data_to_file(OCA_FILENAME, oca_cert, sizeof(csv_cert)) != 0) {
        fprintf(stderr, "Error: Failed to create %s\n", OCA_FILENAME);
        goto cleanup;
    }

    /* Write the signed PEK CSR certificate */
    if (write_data_to_file(SIGNED_PEK_CSR_FILENAME, &pek_csr, sizeof(csv_cert)) != 0) {
        fprintf(stderr, "Error: Failed to create %s\n", SIGNED_PEK_CSR_FILENAME);
        goto cleanup;
    }

    printf("The private key signing certificate file has been created successfully in the current directory.\n");
    printf("Created %s successfully\n", OCA_FILENAME);
    printf("Created %s successfully\n", SIGNED_PEK_CSR_FILENAME);

    ret_val = EXIT_SUCCESS;

cleanup:
    if (oca_cert)
        free(oca_cert);
    if (oca_priv_key)
        EVP_PKEY_free(oca_priv_key);
    if (userid)
        free(userid);
    return ret_val;
}


/**
 * @brief Main function that calls the PEK-OCA handler.
 *
 * @param  [in] argc  The number of command-line arguments.
 * @param  [in] argv  The command-line arguments array.
 *
 * @returns The return value of pek_oca_handler (EXIT_SUCCESS or EXIT_FAILURE).
 */
int main(int argc, char* argv[]) {
    return pek_oca_handler(argc, argv);
}
